Unity渲染管线(三) 深度值、渲染队列

前言

上一章讲了使用 LWRP 的 Renderer Feature 来实现遮挡显示轮廓的效果,这节课对应着来用 Shader 实现,让我们看一下具体 Renderer Feature 完成了 Shader 中的哪些操作

效果图

另外,和上一节的效果相同,存在同一个BUG,使用同一个遮挡显示效果的 GameObject A与B,当都被遮挡住时,两者的轮廓不一定是A在前还是B在前,因为同一队列的两个物体不一定是谁先被渲染,目前没有找到控制该顺序的较好方法。

步骤

  • 编写 “Custom/Shield/ShieldWhite” Pass,用于渲染遮挡后的效果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    ​```Shader "Custom/Shield"
    {
    SubShader
    {
    Tags{ "Queue" = "Geometry+200"}

    Pass
    {
    Name "ShieldWhite"
    Tags { "LightMode" = "ForwardBase" }
    ZTest Greater
    ZWrite off

    CGPROGRAM

    #pragma vertex vert
    #pragma fragment frag

    #include "UnityCG.cginc"
    #include "Lighting.cginc"

    struct appdata
    {
    float4 vertex : POSITION;
    };

    struct v2f
    {
    float4 pos: SV_POSITION;
    };

    v2f vert(appdata v)
    {
    v2f o;
    o.pos = UnityObjectToClipPos(v.vertex);
    return o;
    }
    float4 frag(v2f i) : SV_Target
    {
    return float4(1,1,1,1);
    }
    ENDCG
    }
    }
    Fallback off
    }
  • 选择一个平常的 Shader(这里采用的是简单带光照的一个卡通Shader),并在 SubShader 的第一个 Pass 前调用 “Custom/Shield/ShieldWhite” ,并设置队列使其在大多数不透明物体之后渲染

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    Shader "Master/Toon"
    {
    Properties
    {
    _Diffuse("Diffuse",Color) = (1,1,1,1) // 基础色值
    }
    SubShader
    {
    Tags { "LightMode" = "ForwardBase" "Queue" = "Geometry+200" }

    //重点!!!
    UsePass "Custom/Shield/ShieldWhite"

    Pass
    {
    Tags { "LightMode" = "ForwardBase" }
    ZTest on
    ZWrite on

    CGPROGRAM

    #pragma vertex vert
    #pragma fragment frag

    #include "UnityCG.cginc"
    #include "Lighting.cginc"

    struct appdata
    {
    float4 vertex : POSITION;
    float2 uv : TEXCOORD0;
    float3 normal:NORMAL;
    };

    struct v2f
    {
    float4 pos: SV_POSITION;
    float3 color : Color;
    };

    float4 _Diffuse;

    v2f vert(appdata v)
    {
    v2f o;

    o.pos = UnityObjectToClipPos(v.vertex);

    fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

    fixed3 worldNormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject));

    fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);

    fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));

    o.color = ambient + diffuse;

    return o;
    }

    float4 frag(v2f i) : SV_Target
    {
    return fixed4(i.color,1.0);
    }
    ENDCG
    }


    }
    Fallback off
    }
  • 然后创建两个 材质,把 Shader 挂上去,分别调整下颜色作为区分。在场景中新建两个 Plane,分别作为遮挡面的反面与正面,调整位置,便于查看各种不同情况的效果。
    就可以得到对应的效果了。

原理讲解

  • 首先对于深度值写入来说,我们不希望加入的遮挡显示影响其他物体的渲染,即在渲染遮挡的部分的时候我们不会写入深度。即,渲染遮挡部分的 Pass ZWrite off
  • 我们需要判断片元是否被遮挡,才能进行渲染被遮挡的部分为想要的效果。那我们就需要将 SubShader 队列 尽量设置到后面,只有当其他物体渲染完毕写入深度值后,才可以得知是否需要渲染成被遮挡的样子。即,在被应用的 Shader 的 SubShader Tag{“Queue” = “Geometry+200”},队列尽量设置在后面
  • 被遮挡的部分判断我们使用深度值大于缓冲中的深度值,即,ZTest Greater
  • 由于我们不希望使用同一个遮挡效果的片元之间有互相的遮挡效果,那么我们就应该将遮挡显示轮廓效果的 Pass 放到正常显示的 Pass 之前,不然的话,先渲染了正常显示的部分,写入了深度值,再渲染遮挡现实效果的部分就会出现这样的效果。

大家可以更改一下 UsePass 的位置,更改一下深度测试、深度写入的开关或者更改一下队列值,会对整体的理解更深刻一些,或者使用 Windows—>Analysis—>FrameDebug 窗口一步步渲染看下效果。

总结

可以看到,其实使用 Shader 也比较方便,只需要自定义一个 Pass,但是不能根据层级筛选,并且需要手动在代码中修改,队列设置也需要在项目中规定好,而不是可视化地设置。

0%